(function (window) {
	var utils = {
		// color:rgb或rgba格式
		// opacity: 透明度
		calculateColor: function (color, opacity) {
			if (color.indexOf('#') === 0) {
				var color16 = color.slice(1);
				var r = parseInt(color16.slice(0, 2), 16);
				var g = parseInt(color16.slice(2, 4), 16);
				var b = parseInt(color16.slice(4), 16);
				return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
			} else if (/^rgb\(/.test(color)) {
				return color.replace(/rgb/, 'rgba').replace(')', ",") +
					opacity + ')';
			} else {
				return color.split(',').splice(0, 3).join(',') +
					opacity + ')';
			}
		}
	};
	var arrayUtils = {
		forEach: function (arr, cb, scope) {
			if (typeof Array.prototype.forEach === 'function') {
				arr.forEach(cb, scope);
			} else {
				for (var i = 0, len = arr.length; i < len; i++) {
					cb.apply(scope, [arr[i], i, arr]);
				}
			}
		},
		map: function (arr, cb, scope) {
			if (typeof Array.prototype.map === 'function') {
				return arr.map(cb, scope);
			} else {
				var mapped = [];
				for (var i = 0, len = arr.length; i < len; i++) {
					mapped[i] = cb.apply(scope, [arr[i], i, arr]);
				}
				return mapped;
			}
		}
	};

	var Marker = (function () {
		var M = function (options) {
			this.x = options.x;
			this.y = options.y;
			this.rotation = options.rotation;
			this.style = options.style;
			this.color = options.color;
			this.size = options.size;
			this.borderWidth = options.borderWidth;
			this.borderColor = options.borderColor;
		};

		M.prototype.draw = function (context) {
			context.save();
			context.translate(this.x, this.y);
			context.rotate(this.rotation);

			context.lineWidth = this.borderWidth || 0;
			context.strokeStyle = this.borderColor || '#000';
			context.fillStyle = this.color || '#000';
			// 目前先只支持圆
			context.beginPath();
			if (this.style === 'circle') {
				context.arc(0, 0, this.size, 0, Math.PI * 2, false);
			} else if (this.style === 'arrow') {
				// 将箭头后退，让箭头的尖头指向终点
				context.moveTo(-this.size * 2, -this.size);
				context.lineTo(-this.size * 5 / 4, 0);
				context.lineTo(-this.size * 2, this.size);
				context.lineTo(0, 0);
				context.lineTo(-this.size * 2, -this.size);
			}
			context.closePath();
			context.stroke();
			context.fill();
			context.restore();
		};

		return M;
	})();

	var Arc = (function () {
		var A = function (options) {
			var startX = options.startX,
				startY = options.startY,
				endX = options.endX,
				endY = options.endY;

			var L = Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
			var m = (startX + endX) / 2;
			var n = (startY + endY) / 2;
			var factor = 1.5;

			var centerX = (startY - endY) * factor + m;
			var centerY = (endX - startX) * factor + n;

			var radius = Math.sqrt(Math.pow(L / 2, 2) + Math.pow(L * factor, 2));
			var startAngle = Math.atan2(startY - centerY, startX - centerX);
			var endAngle = Math.atan2(endY - centerY, endX - centerX);

			this.startX = startX;
			this.startY = startY;
			this.endX = endX;
			this.endY = endY;
			this.centerX = centerX;
			this.centerY = centerY;
			this.startAngle = startAngle;
			this.endAngle = endAngle;
			this.radius = radius;
			this.lineWidth = options.width || 1;
			this.strokeStyle = options.color || '#000';
			this.labels = options.labels;
			this.label = options.label;
			this.font = options.font || '12px Arial';
			this.shadowBlur = options.shadowBlur;
			this.val = { ...options };

			var midAngle = (startAngle + endAngle) / 2;
			this.labelXPosition = centerX + radius * Math.cos(midAngle);
			this.labelYPosition = centerY + radius * Math.sin(midAngle);
		};
		A.prototype.draw = function (context) {
			context.save();
			context.lineWidth = this.lineWidth;
			context.strokeStyle = this.strokeStyle;
			context.shadowColor = this.strokeStyle;
			context.shadowBlur = this.shadowBlur || 2;

			context.beginPath();
			context.arc(this.centerX, this.centerY, this.radius, this.startAngle, this.endAngle, false);
			context.stroke();
			context.restore();
	
			context.save();
			context.fillStyle = this.strokeStyle;
			context.font = this.font;
			var title = this.labels.length > 10 ? this.labels.substring(0, 10) + '...' : this.labels
			var metrics = context.measureText(title);
			var width = metrics.width;
			var height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
			if (this.label) {
				context.fillText(
					title,
					this.labelXPosition - width / 2,
					this.labelYPosition + height / 2
				);
			}
			context.restore();
		};

		return A;
	})();

	var Pulse = (function () {
		function P(options) {
			this.x = options.x;
			this.y = options.y;
			this.maxRadius = options.radius;
			this.color = options.color;
			this.shadowBlur = 5;
			this.lineWidth = options.borderWidth;
			this.r = 0;
			this.factor = 2 / options.radius;
		};

		P.prototype.draw = function (context) {
			var vr = 0.5;
			this.r += vr;

			context.save();
			context.translate(this.x, this.y);
			var strokeColor = this.color;
			strokeColor = utils.calculateColor(strokeColor, 1 - this.r / this.maxRadius);
			context.strokeStyle = strokeColor;
			context.shadowBlur = this.shadowBlur;
			context.shadowColor = strokeColor;
			context.lineWidth = this.lineWidth;
			context.beginPath();
			context.arc(0, 0, this.r, 0, Math.PI * 2, false);
			context.stroke();
			context.restore();

			if (Math.abs(this.maxRadius - this.r) < 0.8) {
				this.r = 0;
			}
		}
		return P;
	})();

	var Spark = (function () {
		var S = function (options) {
			var startX = options.startX,
				startY = options.startY,
				endX = options.endX,
				endY = options.endY;

			//两点之间的圆有多个，通过两点及半径便可以定出两个圆，根据需要选取其中一个圆
			var L = Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
			var m = (startX + endX) / 2; // 横轴中点
			var n = (startY + endY) / 2; // 纵轴中点
			var factor = 1.5;

			var centerX = (startY - endY) * factor + m;
			var centerY = (endX - startX) * factor + n;

			var radius = Math.sqrt(Math.pow(L / 2, 2) + Math.pow(L * factor, 2));
			var startAngle = Math.atan2(startY - centerY, startX - centerX);
			var endAngle = Math.atan2(endY - centerY, endX - centerX);

			// 保证Spark的弧度不超过Math.PI
			if (startAngle * endAngle < 0) {
				if (startAngle < 0) {
					startAngle += Math.PI * 2;
					endAngle += Math.PI * 2;
				} else {
					endAngle += Math.PI * 2;
				}
			}

			this.beArrow = options.beArrow;
			this.arrowSize = options.arrowSize || 3;
			this.tailPointsCount = 10; // 拖尾点数
			this.centerX = centerX;
			this.centerY = centerY;
			this.startAngle = startAngle;
			this.endAngle = endAngle;
			this.radius = radius;
			this.lineWidth = options.width || 1;
			this.strokeStyle = options.color || '#fff';
			this.factor = 2 / this.radius;
			this.deltaAngle = (80 / Math.min(this.radius, 400)) / this.tailPointsCount;
			this.trailAngle = this.startAngle;
			this.arcAngle = this.startAngle;

			this.animateBlur = true;

			const size = options.size ? options.size / 2 : 1
			this.marker = new Marker({
				x: 50,
				y: 80,
				rotation: 50 * Math.PI / 180,
				style: 'arrow',
				color: 'rgb(255, 255, 255)',
				size: this.arrowSize,
				borderWidth: size,
				borderColor: this.strokeStyle
			});
		};

		S.prototype.drawArc = function (context, strokeColor, lineWidth, startAngle, endAngle) {
			context.save();
			context.lineWidth = lineWidth;
			// context.lineWidth = 5;
			context.strokeStyle = strokeColor;
			context.shadowColor = this.strokeStyle;
			// context.shadowBlur = 5;
			context.lineCap = "round";
			context.beginPath();
			context.arc(this.centerX, this.centerY, this.radius, startAngle, endAngle, false);
			context.stroke();
			context.restore();
		};

		S.prototype.draw = function (context) {
			var endAngle = this.endAngle;
			// 匀速
			var angle = this.trailAngle + this.factor;
			var strokeColor = this.strokeStyle;
			if (this.animateBlur) {
				this.arcAngle = angle;
			}
			this.trailAngle = angle;
			strokeColor = utils.calculateColor(strokeColor, 0.1);

			// 拖尾阴影
			// this.drawArc(context, strokeColor, this.lineWidth, this.startAngle, this.arcAngle);

			// 拖尾效果
			var count = this.tailPointsCount;
			for (var i = 0; i < count; i++) {
				var arcColor = utils.calculateColor(this.strokeStyle, 0.3 - 0.3 / count * i);
				var tailLineWidth = 5;
				if (this.trailAngle - this.deltaAngle * i > this.startAngle) {
					this.drawArc(context, arcColor,
						tailLineWidth - tailLineWidth / count * i,
						this.trailAngle - this.deltaAngle * i,
						this.trailAngle
					);
				}
			}

			context.save();
			context.translate(this.centerX, this.centerY);

			if( this.beArrow ){
				this.marker.x = Math.cos(this.trailAngle) * this.radius;
				this.marker.y = Math.sin(this.trailAngle) * this.radius;
				this.marker.rotation = this.trailAngle + Math.PI / 2;
				this.marker.draw(context);
			}

			context.restore();

			if ((endAngle - this.trailAngle) * 180 / Math.PI < 0.5) {
				this.trailAngle = this.startAngle;
				this.animateBlur = false;
			}
		};

		return S;
	})();


	var Migration = (function () {
		var M = function (options) {
			this.data = options.data;
			this.store = {
				arcs: [],
				markers: [],
				pulses: [],
				sparks: []
			};
			this.playAnimation = true;
			this.started = false;
			this.context = options.context;
			this.style = options.style;
			this.init();
		};

		M.prototype.init = function () {
			this.updateData(this.data);
		};
		/*
		 * Shape 必须拥有draw方法
		*/
		M.prototype.add = function (Shape) {

		};
		M.prototype.remove = function () {

		};
		M.prototype.clear = function () {
			this.store = {
				arcs: [],
				markers: [],
				pulses: [],
				sparks: []
			};
			// 更新状态
			this.playAnimation = true;
			this.started = false;
			// 清除绘画实例，如果没有这个方法，多次调用start，相当于存在多个动画队列同时进行
			window.cancelAnimationFrame(this.requestAnimationId);
		};
		/*
		 * 更新数据
		*/
		M.prototype.updateData = function (data) {
			if (!data || data.length === 0) {
				return;
			}
			this.clear();
			this.data = data;
			if (this.data && this.data.length > 0) {
				arrayUtils.forEach(this.data, function (element) {
					var arc = new Arc({
						...element,
						startX: element.from[0],
						startY: element.from[1],
						endX: element.to[0],
						endY: element.to[1],
						labels: element.labels,
						label: this.style.arc.label,
						font: this.style.arc.font,
						width: element.arcWidth || this.style.arc.width,
						color: element.color
					});
					var marker = new Marker({
						x: element.to[0],
						y: element.to[1],
						rotation: arc.endAngle + Math.PI / 2,
						style: 'arrow',
						color: element.color,
						size: element.arcWidth || this.style.arc.width + 3,
						borderWidth: 0,
						borderColor: element.color
					});
					var pulse = new Pulse({
						x: element.to[0],
						y: element.to[1],
						radius: this.style.pulse.radius,
						color: element.color,
						borderWidth: this.style.pulse.borderWidth
					});
					var spark = new Spark({
						startX: element.from[0],
						startY: element.from[1],
						endX: element.to[0],
						endY: element.to[1],
						width: 15,
						color: element.color,
						size: element.arcWidth,
						beArrow: element.beArrow,
						arrowSize: element.arrowSize
					});

					this.store.arcs.push(arc);
					element.beArrow && this.store.markers.push(marker);  // 结束点箭头
					element.bePulse && this.store.pulses.push(pulse);  // 结束闪烁点
					this.store.sparks.push(spark);
				}, this);
			}
		};
		/*
		*/
		M.prototype.start = function (canvas) {
			var that = this;
			if (!this.started) {
				(function drawFrame() {
					that.requestAnimationId = window.requestAnimationFrame(drawFrame, canvas);

					if (that.playAnimation) {
						canvas.width += 1;
						canvas.width -= 1;
						for (var p in that.store) {
							var shapes = that.store[p];
							for (var i = 0, len = shapes.length; i < len; i++) {
								shapes[i].draw(that.context);
							}
						}
					}
				})();
				this.started = true;
			}
		};
		M.prototype.play = function () {
			this.playAnimation = true;
		};
		M.prototype.pause = function () {
			this.playAnimation = false;
		};
		return M;
	})();

	L.MigrationLayer = L.Class.extend({
		options: {
			map: {},
			data: {},
			pulseRadius: 25,
			pulseBorderWidth: 3,
			arcWidth: 1,
			arcLabel: true,
			arcLabelFont: '15px sans-serif',
			Marker: {},
			Spark: {}

		},
		_setOptions: function (obj, options) {
			if (!obj.hasOwnProperty('options')) {
				obj.options = obj.options ? L.Util.create(obj.options) : {};
			}
			for (var i in options) {
				obj.options[i] = options[i];
			}
			return obj.options;
		},
		initialize: function (options) {
			this._setOptions(this, options);
			this._map = this.options.map || {};
			this._data = this.options.data || {};
			this._style = {
				pulse: {
					radius: this.options.pulseRadius,
					borderWidth: this.options.pulseBorderWidth
				},
				arc: {
					width: this.options.arcWidth,
					label: this.options.arcLabel,
					font: this.options.arcLabelFont
				}
			} || {};
			this._show = true;
			this._init();
		},
		_init: function () {
			var container = L.DomUtil.create('div', 'leaflet-ODLayer-container');
			container.style.position = 'absolute';
			container.style.width = this._map.getSize().x + "px";
			container.style.height = this._map.getSize().y + "px";

			this.container = container;

			this.canvas = document.createElement('canvas');
			this.context = this.canvas.getContext('2d');
			container.appendChild(this.canvas);
			this._map.getPanes().overlayPane.appendChild(container);
			//this._resize();
			if (!this.migration) {
				var data = this._convertData();
				this.migration = new Migration({
					data: data,
					context: this.context,
					style: this._style
				});
				//this._draw();
				//this._bindMapEvents();
			}
		},
		_resize: function () {
			var bounds = this._map.getBounds();
			var topleft = bounds.getNorthWest();
			var topLeftscreen = this._map.latLngToContainerPoint(topleft);
			//当地图缩放或者平移到整个地图的范围小于外层容器的范围的时候，要对this.container进行上下平移操作，反之则回归原位
			if (topLeftscreen.y > 0) {
				this.container.style.top = -topLeftscreen.y + 'px';
			} else {
				this.container.style.top = '0px';
			}

			var containerStyle = window.getComputedStyle(this._map.getContainer());
			this.canvas.setAttribute('width', parseInt(containerStyle.width, 10));
			this.canvas.setAttribute('height', parseInt(containerStyle.height, 10));
		},
		_convertData: function () {
			var bounds = this._map.getBounds();
			let maxValue;
			let minValue
			if (this._data && bounds) {
				arrayUtils.forEach(this._data, function (d) {
					if (d.value) {
						if (!maxValue) {
							maxValue = d.value;
							minValue = d.value;
						}
						if (maxValue < d.value) {
							maxValue = d.value;
						}
						if (minValue > d.value) {
							minValue = d.value;
						}
					}
				});
				var maxWidth = this.options.maxWidth || 10;
				var data = arrayUtils.map(this._data, function (d) {
					if (d.value) {
						if (!maxValue) {
							maxValue = d.value;
							minValue = d.value;
						}
						if (maxValue < d.value) {
							maxValue = d.value;
						}
						if (minValue > d.value) {
							minValue = d.value;
						}
					}

					var fromPixel = this._map.latLngToContainerPoint(new L.LatLng(d.from[1], d.from[0]));
					var toPixel = this._map.latLngToContainerPoint(new L.LatLng(d.to[1], d.to[0]));
					return {
						...d,
						from: [fromPixel.x, fromPixel.y],
						to: [toPixel.x, toPixel.y],
						labels: d.labels,
						value: d.value,
						color: d.color,
						arcWidth: d.value ? parseInt((d.value - minValue) * (maxWidth - 1) / (maxValue - minValue)) + 1 : this.options.arcWidth,
						beArrow: d.beArrow,
						arrowSize: d.arrowSize,
						bePulse: d.bePulse
					}
				}, this);
				return data;
			}
		},
		_bindMapEvents: function () {
			var that = this;
			// window.onresize = function () {
			//     that._resize();
			// };
			// this._map.on('movestart ', function () {
			//     that.migration.pause();
			// });
			this._map.on('moveend', function () {
				that.migration.play();
				that._draw();
			});
			this._map.on('zoomstart ', function () { that.container.style.display = 'none' });
			this._map.on('zoomend', function () {
				if (that._show) {
					that.container.style.display = ''
					that._draw();
				}
			});
		},
		_draw: function () {
			var bounds = this._map.getBounds();
			if (bounds && this.migration.playAnimation) {
				this._resize();
				this._transform();
				var data = this._convertData();
				this.migration.updateData(data);
				this.migration.start(this.canvas);
			}
		},
		_transform: function () {
			var bounds = this._map.getBounds();
			var topLeft = this._map.latLngToLayerPoint(bounds.getNorthWest());
			L.DomUtil.setPosition(this.container, topLeft);
		},
		addTo: function () {
			this._bindMapEvents();
			var bounds = this._map.getBounds();
			if (bounds && this.migration.playAnimation) {
				this._resize();
				this._transform();

				var data = this._convertData();
				this.migration.updateData(data);
				this.migration.start(this.canvas);
			}
		},
		onClick: function (callBack) {
			const isPointInArc = (x, y, arc) => {
				this.context.beginPath();
				this.context.lineWidth = (arc.lineWidth > 6) ? arc.lineWidth : 12;  // 值越大越好触发点击事件(方便选中线)
				this.context.arc(arc.centerX, arc.centerY, arc.radius, arc.startAngle, arc.endAngle, false);
				return this.context.isPointInStroke(x, y);
			}
			let that = this;
			this.canvas.addEventListener('click', function (event) {
				var rect = that.canvas.getBoundingClientRect();
				var x = event.clientX - rect.left;
				var y = event.clientY - rect.top;
				that.migration.store.arcs.forEach((circle, index) => {
					if (isPointInArc(x, y, circle)) {
						callBack(circle);
					}
				});
			})
		},
		setOptions: function (newOptions) {
			// 更新实例的 options 属性
			this._setOptions(this, newOptions);
			// 更新与样式相关的属性
			this._style.pulse.radius = this.options.pulseRadius;
			this._style.pulse.borderWidth = this.options.pulseBorderWidth;
			this._style.arc.width = this.options.arcWidth;
			this._style.arc.label = this.options.arcLabel;
			this._style.arc.font = this.options.arcLabelFont;
			// 触发重绘以应用新的配置
			this._draw();
		},
		setData: function (data) {
			this._data = data;
			this._draw();
		},
		hide: function () {
			this.container.style.display = 'none';
			this._show = false;
		},
		show: function () {
			this.container.style.display = '';
			this._show = true;
		},
		play: function () {
			this.migration.play();
		},
		pause: function () {
			this.migration.pause();
		},
		destroy: function () {
			this.migration.clear();
			//移除dom
			this.container.parentNode.removeChild(this.container);
			//移除事件监听
			this._map.clearAllEventListeners();
			this.mapHandles = [];
		}

	});
	L.migrationLayer = function (options) {
		return new L.MigrationLayer(options)
	}
})(window)
